package edu.mse.camel.client.whiteboard.collaboration;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.ChangeDescription;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.workspace.AbstractEMFOperation;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramCommandStack;
import org.eclipse.swt.widgets.Display;

import edu.mse.camel.client.core.events.model.ModelUpdateEvent2;
import edu.mse.camel.client.core.events.model.ModelUpdateEvent2.KIND;
import edu.mse.camel.client.eventbus.pubsub.ISubscriber;
import edu.mse.camel.client.events.IEvent;
import edu.mse.camel.client.log.Debug;
import edu.mse.camel.client.whiteboard.diagram.part.WhiteboardDiagramEditor;

public class WB_ModelUpdateEventHandler implements ISubscriber {

	private ResourceSet root;
	TransactionalEditingDomain transactionalEditingDomain;
	IOperationHistory history;
	
	public WB_ModelUpdateEventHandler(ResourceSet root, TransactionalEditingDomain editingDomain, IOperationHistory history) {
		this.root = root;
		this.transactionalEditingDomain = editingDomain;
		this.history = history;
	}
	
	private void beginCacheNotificationForAll(WB_CamelNotificationRecorder recorder) {
		// the resourseSet itself is also a notifier
		recorder.install(root);
		for(Resource res : root.getResources()) {
			TreeIterator<EObject> tree = 
				EcoreUtil.getAllContents(Collections.singleton(res));
			while (tree.hasNext()) {
				Object obj = tree.next();
				if(obj instanceof Notifier) {
					Notifier notifier = (Notifier) obj;
					recorder.install(notifier);
				}
			}
		}
	}
		
	private void setDelivery(boolean deliver) {
		// the resourseSet itself is also a notifier
		root.eSetDeliver(deliver);
		for(Resource res : root.getResources()) {
			TreeIterator<EObject> tree = 
				EcoreUtil.getAllContents(Collections.singleton(res));
			while (tree.hasNext()) {
				Object obj = tree.next();
				if(obj instanceof Notifier) {
					Notifier notifier = (Notifier) obj;
					notifier.eSetDeliver(deliver);
				}
			}
		}
	}
	
	private class EMFOperation extends AbstractEMFOperation {
		ChangeDescription cd;
		
		public EMFOperation(TransactionalEditingDomain domain, String label, ChangeDescription cd) {
			super(domain, label);
			this.cd = cd;
		}

		@Override
		protected IStatus doExecute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {

			try {
				WB_CamelNotificationRecorder recorder = new WB_CamelNotificationRecorder();
				//setDelivery(false);
				Debug.log_info(WhiteboardDiagramEditor.ID, "Attaching notification recorder");
				beginCacheNotificationForAll(recorder);
				Debug.log_info(WhiteboardDiagramEditor.ID, "Applying the change");
				cd.apply();
				Debug.log_info(WhiteboardDiagramEditor.ID, "Applying notifications");
				recorder.applyAndUninstallForAll();
				Debug.log_info(WhiteboardDiagramEditor.ID, "Change done");
				//setDelivery(true);
			} catch (Exception e) {
				Debug.log_warning(WhiteboardDiagramEditor.ID, "Failed to apply on target");
				e.printStackTrace();
			}
			
			return Status.OK_STATUS;
		}

	}
	
	/* (non-Javadoc)
	 * @see edu.mse.camel.client.eventbus.pubsub.ISubscriber#notifyEvent(edu.mse.camel.client.events.IEvent)
	 * 
	 * This method is synchronized because it may be called from the EventManager
	 * at two points:
	 * 1. While it is publishing the cached events.
	 * 2. Or during normal operation.
	 * 
	 * We don't synchronize other methods since they are all private. However, we must make sure
	 * that the model is modified from here only!
	 * TODO: Keep this in mind when implementing the role change feature.
	 * 
	 */
	@SuppressWarnings("unchecked")
	@Override
	public synchronized void notifyEvent(final IEvent e) {
		if (e instanceof ModelUpdateEvent2) {
			final ModelUpdateEvent2 me2 = (ModelUpdateEvent2) e;
			
			if(me2.getKind() != KIND.WB) {
				// this event is not for us
				return;
			}
			
			ByteArrayInputStream bais = new ByteArrayInputStream(me2.getByteArray());
			
			try {
				ResourceSet rSet = root;
				Resource res = rSet.createResource(rSet.getResources().get(1).getURI());
				
				Map option = new HashMap();
				option.put(XMLResource.OPTION_PROCESS_DANGLING_HREF,
						XMLResource.OPTION_PROCESS_DANGLING_HREF_THROW);
				res.load(bais, option);
				
				final ChangeDescription cd = (ChangeDescription) res.getContents().get(0);
				
				EMFOperation operation = new EMFOperation(transactionalEditingDomain, "hello recorder label", cd);
				
				try {
					history.execute(operation, null, null);
				} catch (ExecutionException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				
				/*
				Display.getDefault().syncExec(new Runnable() {
					
					@Override
					public void run() {
						try {
							WB_CamelNotificationRecorder recorder = new WB_CamelNotificationRecorder();
							//setDelivery(false);
							Debug.log_info(WhiteboardDiagramEditor.ID, "Attaching notification recorder");
							beginCacheNotificationForAll(recorder);
							Debug.log_info(WhiteboardDiagramEditor.ID, "Applying the change");
							cd.apply();
							Debug.log_info(WhiteboardDiagramEditor.ID, "Applying notifications");
							recorder.applyAndUninstallForAll();
							Debug.log_info(WhiteboardDiagramEditor.ID, "Change done");
							//setDelivery(true);
						} catch (Exception e) {
							Debug.log_warning(WhiteboardDiagramEditor.ID, "Failed to apply on target");
							e.printStackTrace();
						}
					}
					
				});
				*/
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}

}
